﻿using System;
using System.Collections;
using System.Collections.Generic;
using GeneticAlgorithms.Alphabets;

namespace GeneticAlgorithms.Genomes
{
    /// <summary>
    /// Binary genome, uses a <see cref="BitArray"/> for
    /// underlying implementation.
    /// </summary>
    public class BinaryGenome : IGenome<bool>
    {
        #region Fields

        /// <summary>
        /// This BitArray field will contain all
        /// the genome's genes.
        /// </summary>
        private readonly BitArray genes;

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="BinaryGenome"/> class.
        /// </summary>
        /// <param name="length">The length.</param>
        public BinaryGenome(int length)
        {
            genes = new BitArray(length);
            Alphabet = new BinaryAlphabet();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BinaryGenome"/> class.
        /// </summary>
        /// <param name="genes">The genes.</param>
        private BinaryGenome(BitArray genes)
        {
            genes = new BitArray(genes);
            Alphabet = new BinaryAlphabet();
        }

        #region IEnumerable<bool> Members

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<bool> GetEnumerator()
        {
            foreach (bool gene in genes)
            {
                yield return gene;
            }
        }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return genes.GetEnumerator();
        }

        #endregion

        #region IGenomeBase Members

        /// <summary>
        /// Gets or sets the genome's fitness.
        /// </summary>
        /// <value>The fitness.</value>
        public double? Fitness 
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the genome's length (total number of genes in it).
        /// </summary>
        /// <value>The length.</value>
        public int Length
        {
            get { return genes.Length; }
        }

        #endregion

        #region ICloneable Members

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        public object Clone()
        {
            return new BinaryGenome(this.genes);
        }

        #endregion

        #region IGenome<bool> Members

        /// <summary>
        /// Gets the genome alphabet.
        /// </summary>
        /// <value>The genome alphabet.</value>
        /// <remarks>
        /// A genetic alphabet describes all values the genes may have.
        /// </remarks>
        public IAlphabet<bool> Alphabet
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets the gene at the 
        /// specified <paramref name="index"/>.
        /// </summary>
        /// <param name="index">
        /// Index of the gene.
        /// </param>
        /// <value>
        /// The gene at the specified <paramref name="index"/>.
        /// </value>
        public bool this[int index]
        {
            get { return genes[index]; }
            set { genes[index] = value; }
        }

        #endregion

        /// <summary>
        /// Converts the contents of the genome to an array of bytes
        /// </summary>
        /// <returns>
        /// A byte array, where each byte contains 8 of the
        /// genome's genes.
        /// </returns>
        public byte[] GetBytes()
        {
            int length = sizeof(byte) * 8;
            int vectorLength = (int)Math.Ceiling((double)Length / length);
            byte[] result = new byte[vectorLength];

            int index = 0;

            for (int i = 0; i < vectorLength; ++i)
            {
                byte value = 0;

                for (int j = 0; j < length; ++j, ++index)
                {
                    if (genes[index])
                    {
                        value |= (byte)(1 << j);
                    }
                }

                result[i] = value;
            }

            return result;
        }
    }
}
